﻿//
// Created by LiuYou on 2021/4/24.
//

#ifndef EFFECTIVEMORDENCPP_WIDGET_HPP
#define EFFECTIVEMORDENCPP_WIDGET_HPP


#define CPP_MODERN_PIMPL
//#define CPP98_PIMPL
//#define ORIGIN



#ifdef CPP_MODERN_PIMPL

#include <memory>
// 在这里导入头文件其实也没有问题, 因为头文件不会进行修改。但是尽量能不导入就不导入, 除非必要！
// 但是是万万不能导入开发者自己写的头文件的！！！！
#include <vector>
#include <ostream>

// TODO 注意: 这里也非常重要！！！！
//  因为class CustomClass;中的CustomClass并不是CppModernPimpl::Widget::something(并不是命名空间CppModernPimpl内的类型)。
//  所以一定一定一定(重要的事情说三遍！)要前置声明在CppModernPimpl命名空间外面！！！！
//  这一点是和struct WidgetImpl;不同的因为WidgetImpl就是命名空间的一个类中的类型, 所以它的前置声明可以放到类中！！！！
class CustomClass;

namespace CppModernPimpl {
    class Widget {
    public:
        // TODO 注意: 因为WidgetImpl就是命名空间的一个类中的类型, 所以它的前置声明可以放到类中！！！！
        struct WidgetImpl;


    public:
        Widget();

        // ==================================================

        Widget(const std::string& name, const CustomClass& customClass);

        // ==================================================

        // TODO 注意: 非常重要！！！！
        //  如果每一个这个析构函数, 是编译失败的！
        //  而且析构函数的定义要放在实现文件中(*.cpp文件)！
        //  报错信息为:
        //  /usr/lib/gcc/x86_64-pc-cygwin/9.3.0/include/c++/bits/unique_ptr.h:79:16: error: invalid application of 'sizeof' to incomplete type 'CppModernPimpl::Widget::WidgetImpl'
        //    79 |  static_assert(sizeof(_Tp)>0,
        //       |                ^~~~~~~~~~~
        //  错误：无效的'sizeof'应用到不完整的类型'CppModernPimpl :: Widget :: WidgetImpl'
        // todo 注意: 该条款是
        //  利用std::unique_ptr智能指针使用Pimple时, 将特殊成员函数(Big-Five)的定义放到实现文件中
        //  所以, 如果是复制Widget对象的话需要自己将Big-Five的成员函数显式声明定义出来(如果编译器生成的版本不能满足, 那就需要我们自己去编写！)
        virtual ~Widget();

        // ==================================================

        // TODO 注意: 例如: 如果想要Widget可以复制, 支持复制操作。就不能使用编译器生成的(=default)版本！因为它是只能复制std::unique_ptr(即, 浅拷贝),
        //  但是我们希望的是可以拷贝std::unique_ptr指向的内容(即, 进行深拷贝！)
        Widget(const Widget& rhs);

        // TODO 注意: 深拷贝
        Widget& operator=(const Widget& rhs);

        // ==================================================

        // 其它接口
        const std::string& getName() const;

        const std::vector<double>& getData() const;

        // 需要前置声明class CustomClass;
        const CustomClass& getCustomClass() const;

        // ==================================================

        friend std::ostream& operator<<(std::ostream& os, const Widget& widget);

        // ==================================================

        // TODO 注意:
        //  const std::unique_ptr<WidgetImpl>& getPtr() const;
        //  std::unique_ptr<WidgetImpl>& getPtr();
        //  这两个函数没有必要设计出来！！！！
//        const std::unique_ptr<WidgetImpl>& getPtr() const;
        const std::unique_ptr<WidgetImpl>& getPtr() const = delete;

//        std::unique_ptr<WidgetImpl>& getPtr();
        std::unique_ptr<WidgetImpl>& getPtr() = delete;

        // ==================================================

        // ...其它函数

    private:
        // TODO 注意: 前置声明类最好放在类的开始, 而且是public:下！！！！
//        struct WidgetImpl;
        std::unique_ptr<WidgetImpl> ptr;
//        WidgetImpl* ptr;
    };

    // ==================================================

//    std::ostream& operator<<(std::ostream& os, const Widget& widget);

    // ==================================================

    // ...其它函数

}
#endif


#ifdef CPP98_PIMPL
/*
 * C++98的代码, 它们散发着上一个千年的腐朽气息。其中使用了裸指针、裸new运算符和裸delete运算符, 一切都是赤裸裸。
 */
namespace Cpp98Pimpl {
    class Widget {
    public:
        Widget();

        virtual ~Widget();
        // ...
    private:
        struct WidgetImpl;
        WidgetImpl* ptr;
    };
}
#endif


#ifdef ORIGIN
// 这些头文件增加了Widget的客户的编译时间, 而且也使得这些客户依赖于这些头文件。
// 如果某个头文件的内容发生了改变, 则Widget的客户必须重新编译。
// std::string和std::vector不会改动, 但是自定义的类CustomClass很有可能修改升级。
#include <string>
#include <vector>
#include "CustomClass.hpp"

namespace Original {


    class Widget {
    public:
        Widget();
        // ...
    private:
        std::string name;
        std::vector<double> data;
        CustomClass customClass;
    };

}
#endif


#endif //EFFECTIVEMORDENCPP_WIDGET_HPP
